using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;

namespace CosyTrade.Data
{
    [Serializable]
    public abstract class UndoableBase : IUndoable,IBusiness,IORM
    {
        readonly object accessLock = new object();

        [NonSerialized]
        private Stack<Hashtable> sourceStack;

        [NonSerialized]
        private Stack<Hashtable> stateStack;

        private bool isDelete;

        private bool isModify;

        private bool isNew = true;

        private bool isDetached = true;

        [Browsable(false)]
        protected Stack<Hashtable> StateStack
        {
            get 
            {
                if (stateStack == null)
                    lock (accessLock)
                    {
                        if (stateStack == null)
                            stateStack = new Stack<Hashtable>();
                    }
                return stateStack; 
            }
        }

        [Browsable(false)]
        protected Stack<Hashtable> SourceStack
        {
            get 
            {
                if (sourceStack == null)
                    lock (accessLock)
                    {
                        if (sourceStack == null)
                            sourceStack = new Stack<Hashtable>();
                    }
                return sourceStack; 

            }
        }

        [Browsable(false)]
        public bool IsNew
        {
            get { return isNew; }
            internal set { isNew = value; }
        }

        [Browsable(false)]
        public bool IsDetached
        {
            get { return isDetached; }
            internal set { isDetached = value; }
        }

        [Browsable(false)]
        public bool IsChange
        {
            get { return IsNew || IsDelete || IsModify; }
        }

        [Browsable(false)]
        public bool IsDelete
        {
            get { return isDelete; }
            internal set { isDelete = value; }
        }

        [Browsable(false)]
        public int EditLevel
        {
            get { return StateStack.Count; }
        }

        #region IUndoable Members

        public void CopyState()
        {
            Type currentType = GetType();
            Hashtable state = new Hashtable();

            PropertyInfo[] Propertys = currentType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            while (currentType != typeof (UndoableBase))
            {
                foreach (PropertyInfo property in Propertys)
                {
                    if (property.DeclaringType == currentType)
                    {
                        if (!IsSupportUndoableProperty(property)) continue;
                        object value = property.GetValue(this, null);

                        if (typeof (IUndoable).IsAssignableFrom(property.PropertyType))
                        {
                            if (value == null)
                                state.Add(GetPropertyName(property), null);
                            else
                                ((IUndoable) value).CopyState();
                        }
                        else state.Add(GetPropertyName(property), value);
                    }
                }
                currentType = currentType.BaseType;
            }

            StateStack.Push(state);
            if (SourceStack.Count <= 0) SourceStack.Push(state);
        }

        public void UndoChanges()
        {
            if (StateStack.Count <= 0)
            {
                CopyState();
                return;
            }

            Type currentType = GetType();
            Hashtable state = StateStack.Pop();

            while (currentType != typeof (UndoableBase))
            {
                PropertyInfo[] propertyInfos =
                    currentType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

                foreach (PropertyInfo property in propertyInfos)
                {
                    if (!IsSupportUndoableProperty(property)) continue;

                    if (property.DeclaringType == currentType)
                    {
                        object value = property.GetValue(this, null);
                        if (typeof(IUndoable).IsAssignableFrom(property.PropertyType))
                        {
                            if (value == null && !state.Contains(GetPropertyName(property)))
                            {
                                value = Activator.CreateInstance(property.PropertyType);
                                property.SetValue(this, null, null);
                            }

                            if (!state.Contains(GetPropertyName(property)))
                            {
                                IUndoable undoable = (IUndoable)value;
                                if (undoable != null) undoable.UndoChanges();
                            }
                        }
                        else if (property.CanWrite) property.SetValue(this, state[GetPropertyName(property)], null);
                    }
                }
                currentType = currentType.BaseType;
            }
        }

        public void AffrimChanges()
        {
            isDetached = false;
            if (SourceStack.Count <= 0) return;
            if (EditLevel > 0) StateStack.Pop();
            Type currentType = GetType();
            isModify = false;

            Hashtable state = SourceStack.ToArray()[0];

            while (currentType != typeof (UndoableBase))
            {
                PropertyInfo[] propertyInfos =
                    currentType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                foreach (PropertyInfo property in propertyInfos)
                {
                    if (property.DeclaringType == currentType)
                    {
                        if (!IsSupportUndoableProperty(property)) continue;
                        object value = property.GetValue(this, null);
                        if (typeof (IBusiness).IsAssignableFrom(property.PropertyType)
                            ||(value!=null && value is IBusiness))
                        {
                            if (value != null)
                            {
                                ((IBusiness)value).AffrimChanges();
                                isModify = isModify || ((IBusiness) value).IsChange;
                            }
                        }
                        else
                        {
                            string stateName = GetPropertyName(property);
                            object stateValue = state.Contains(stateName) ? state[stateName] : null;
                            object propertyValue = property.GetValue(this, null);
                            isModify = isModify || !Equals(stateValue, propertyValue);
                        }
                    }
                }
                currentType = currentType.BaseType;
            }
        }

        public void AcceptChanges()
        {
            Type currentType = GetType();

            while (currentType != typeof (UndoableBase))
            {
                PropertyInfo[] propertyInfos =
                    currentType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                foreach (PropertyInfo property in propertyInfos)
                {
                    if (property.DeclaringType == currentType)
                    {
                        if (!IsSupportUndoableProperty(property)) continue;
                        object value = property.GetValue(this, null);
                        if (typeof (IBusiness).IsAssignableFrom(property.PropertyType)
                            || (value != null && value is IBusiness))
                        {
                            if (value != null) ((IBusiness) value).AcceptChanges();
                        }
                    }
                }
                currentType = currentType.BaseType;
            }
            SourceStack.Clear();
            if (EditLevel > 0) sourceStack.Push(StateStack.Pop());
            else ((IUndoable) this).CopyState();
            StateStack.Clear();
            ClearFlags();
        }

        public void RejectChanges()
        {
            Type currentType = GetType();

            while (currentType != typeof (UndoableBase))
            {
                PropertyInfo[] propertyInfos =
                    currentType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                foreach (PropertyInfo property in propertyInfos)
                {
                    if (property.DeclaringType == currentType)
                    {
                        if (!IsSupportUndoableProperty(property)) continue;
                        object value = property.GetValue(this, null);
                        if (typeof (IBusiness).IsAssignableFrom(property.PropertyType)
                            || (value != null && value is IBusiness))
                        {
                            if (value != null) ((IBusiness) value).RejectChanges();
                        }
                        else
                        {
                            Hashtable state = SourceStack.ToArray()[0];
                            StateStack.Push(state);
                            ((IUndoable) this).UndoChanges();
                        }
                    }
                }
                currentType = currentType.BaseType;
            }

            StateStack.Clear();
            ClearFlags();
        }

        public IORM GetChanges()
        {
            ((IBusiness) this).AffrimChanges();

            Type currentType = GetType();
            object cloneObject = Activator.CreateInstance(currentType);

            PropertyInfo[] Propertys = currentType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            while (currentType != typeof (UndoableBase))
            {
                foreach (PropertyInfo property in Propertys)
                {
                    if (property.DeclaringType == currentType)
                    {
                        if (!IsSupportUndoableProperty(property)) continue;
                        object value = property.GetValue(this, null);

                        if (typeof(IBusiness).IsAssignableFrom(property.PropertyType)
                            ||(value!=null && value is IBusiness))
                        {
                            if (value == null) property.SetValue(this, null, null);
                            else
                            {
                                IBusiness business = (IBusiness)value;
                                IORM propertyvalue = business.GetChanges();
                                if (propertyvalue == null && value is UndoableBase)
                                    propertyvalue = (IORM)value;
                                property.SetValue(cloneObject, propertyvalue, null);
                            }
                        }
                        else
                        {
                            if (property.CanWrite)
                                property.SetValue(cloneObject, value, null);
                            else
                            {
                                FieldInfo privateField =
                                    currentType.GetField(property.Name,
                                                         BindingFlags.IgnoreCase | BindingFlags.NonPublic |
                                                         BindingFlags.SetField | BindingFlags.Instance |
                                                         BindingFlags.DeclaredOnly);
                                if (privateField != null)
                                    privateField.SetValue(cloneObject, value);
                            }
                        }
                    }
                }
                currentType = currentType.BaseType;
            }

            ((UndoableBase) cloneObject).isDelete = isDelete;
            ((UndoableBase) cloneObject).isNew = isNew;
            ((UndoableBase) cloneObject).isModify = isModify;
            if (!IsNew && !isModify && !IsDelete) return null;
            return (IORM) cloneObject;
        }

        [Browsable(false)]
        public bool IsModify
        {
            get { return isModify; }
            internal set { isModify = value; }
        }

        public void ClearFlags()
        {
            isNew = false;
            isDelete = false;
            isModify = false;
            isDetached = false;
        }

        #endregion

        protected static string GetPropertyName(PropertyInfo member)
        {
            return member.DeclaringType.Name + "!" + member.Name;
        }

        protected virtual bool IsSupportUndoableProperty(PropertyInfo propertyInfo)
        {
            return !Attribute.IsDefined(propertyInfo, typeof(DisableUndoableAttribute));
        }
    }
}